package com.syf.match.impl;

import com.syf.domain.ExchangeTrade;
import com.syf.enums.OrderDirection;
import com.syf.match.MatchService;
import com.syf.match.MatchServiceFactory;
import com.syf.match.MatchStrategy;
import com.syf.model.*;
import com.syf.rocket.Source;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 对于消费ringBuffer中消息--根据订单策略的处理：【OrderEventHandler 使用】
 *
 * afterPropertiesSet()方法：
 *      初始化bean的时候执行，可以针对某个具体的bean进行配置。
 *      afterPropertiesSet 必须实现 InitializingBean接口。
 *      实现 InitializingBean接口必须实现afterPropertiesSet方法。
 *
 * 买单逻辑：
 *      1.我们给出一个最优买单价格30元，买10个
 *      2.别人给的卖出价格为 10元--2个，20元--6个，35元--4个
 *      3.我们比较别人卖出价格与我们买入的最优价格， 30 >10和20，把这两个买了
 *      4.若买够10个了则不再买，不够(卖了10元和20元的才8个，不够10个)，但 35元 > 30,则也不买
 *
 */
@Service
@Slf4j
public class LimitPriceMatchServiceImpl implements MatchService, InitializingBean {

    /**
     * 应为要处理 orderBooks，不能在此直接注入 orderBooks对象，会导致 多线程同时处理一个公共变量
     * --》 每一个线程OrderEventHandler 都只处理其对应的 orderBooks(有多个，与symbol交易对 对应)
     */


    @Autowired
    private Source source;

    /**
     * 【0】添加撮合策略：
     *      该类Bean在生成之前，就提前执行了该方法
     *      从BeanPostProcessor的作用，可以看出最先执行的是postProcessBeforeInitialization，
     *      然后是afterPropertiesSet，然后是init-method，然后是postProcessAfterInitialization。
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        MatchServiceFactory.addMatchService(MatchStrategy.LIMIT_PRICE, this);
    }




    /**
     * 【1】进行订单的撮合交易【买单 与 挂单】
     *         如果当前委托单是 买单，那所有卖单都是 挂单
     *
     *    第一个委托单过来，发现orderBooks中的挂单列表为空，无法撮合，则把该委托单添加进 挂单队列和盘口数据中
     *    第一次进行撮合没交易圆满的 委托单才会进入 盘口数据(10元价格，有100个【所有的该价格挂单s 的数量总和】)
     *
     *    进行撮合交易： 卖10元的订单集合MergeOrder 中有很多个小小大大的order, 一个一个order进行处理
     *
     *
     * @param orderBooks
     * @param order
     */
    @Override
    public void match(OrderBooks orderBooks, Order order) {

        if (order.isCancelOrder()) {
            //1.取消一个委托订单(List中删除委托订单)
            orderBooks.cancelOrder(order);
            //2.通过MQ 通知exchange-service模块 修改该委托单状态
            Message<String> message = MessageBuilder.withPayload(order.getOrderId()).build();
            source.cancelOrderOut().send(message);
            //3.结束
            return;
        }


        // 1 进行数据的校验
        if (order.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        // 2 获取一个挂单队列：【如果当前委托单是 买单，那所有卖单都是 挂单】
        Iterator<Map.Entry<BigDecimal, MergeOrder>> markerQueueIterator = null;
        if (order.getOrderDirection() == OrderDirection.BUY) {
            markerQueueIterator = orderBooks.getCurrentLimitPriceIterator(OrderDirection.SELL);
        } else {
            markerQueueIterator = orderBooks.getCurrentLimitPriceIterator(OrderDirection.BUY);
        }


        // 是否退出循环
        boolean exitLoop = false;
        // 已经完成的委托订单
        List<Order> completedOrders = new ArrayList<>();
        // 产生的交易记录
        List<ExchangeTrade> exchangeTrades = new ArrayList<>();

        // 3 循环我们的 挂单队列
        while (markerQueueIterator.hasNext() && !exitLoop) {
            Map.Entry<BigDecimal, MergeOrder> markerOrderEntry = markerQueueIterator.next();
            BigDecimal markerPrice = markerOrderEntry.getKey();
            MergeOrder markerMergeOrder = markerOrderEntry.getValue();

            // 我花10 块钱买东西 ,别人的东西如果大于10 块 ,我就买不了
            if (order.getOrderDirection() == OrderDirection.BUY && order.getPrice().compareTo(markerPrice) < 0) {
                break;
            }
            // 我出售一个东西 10 ,结果有个人花5块钱
            if (order.getOrderDirection() == OrderDirection.SELL && order.getPrice().compareTo(markerPrice) > 0) {
                break;
            }

            //同一价格的商品订单【有多个】
            Iterator<Order> markerIterator = markerMergeOrder.iterator();
            while (markerIterator.hasNext()) {
                Order marker = markerIterator.next();

                //进行交易订单，返回 完成的交易记录
                ExchangeTrade exchangeTrade = processMath(order, marker, orderBooks);
                //添加交易记录
                exchangeTrades.add(exchangeTrade);

                //订单是否完成的判断依据(方法： 委托量-成交量)
                if (order.isCompleted()) { // 经过一圈的吃单,我吃饱了【买够了，不再买】
                    completedOrders.add(order);  //该委托订单order 已经完成【买的完成】
                    exitLoop = true; // 退出最外层的循环
                    break;  // 退出当前的MergeOrder的循环
                }

                if (marker.isCompleted()) {// MergeOrder 的一个小的订单完成了
                    completedOrders.add(marker); //【卖完了 (可能人家买100个，你只买了10个，你完成别人没完成)】
                    markerIterator.remove();
                }

            }

            if (markerMergeOrder.size() == 0) { // MergeOrder 已经吃完了
                markerQueueIterator.remove(); // 将该MergeOrder 从树上移除掉
            }

        }

        // 4 若我们的订单没有完成(买入或卖出量 - 成交量)
        if (order.getAmount().compareTo(order.getTradedAmount()) > 0) {
            //根据价格 添加一个委托订单 进入我们的 卖出/买入队列里面，并添加进盘口中
            orderBooks.addOrder(order);  //【orderBooks的数据怎么来？】
        }

        if (exchangeTrades.size() > 0) {  //否则发送一个 Null 的集合
            // 5 产生交易记录                                                 (右边)显示--交易完成的记录
            handlerExchangeTrades(exchangeTrades);

        }
        if (completedOrders.size() > 0) {
            // 6 处理已经成交的委托订单【自己完成的交易委托单/商家的委托单】     (左下边)显示--历史委托单
            completedOrders(completedOrders);
        }


    }

    /**
     * 【1.1】进行委托单的匹配撮合交易（最核心）
     *          进行撮合交易： 卖10元的订单集合MergeOrder 中有很多个小小大大的order, 一个一个order进行处理
     *          每次撮合完成后,都会产生一个成交记录
     *
     * @param taker  吃单
     * @param marker 挂单
     * @return ExchangeTrade 交易记录
     */
    private ExchangeTrade processMath(Order taker, Order marker, OrderBooks orderBooks) {
        // 1 定义交易的变量
        // 1)成交的价格 dealPrice
        BigDecimal dealPrice = marker.getPrice();

        // 2)成交的数量 turnoverAmount
        BigDecimal turnoverAmount = BigDecimal.ZERO;
        // 本次需要的数量(需要20个，可能已经买了16个，还需要4个)
        BigDecimal needAmount = calcTradeAmount(taker); // 10  20
        // 本次提供给你的数量
        BigDecimal providerAmount = calcTradeAmount(marker); // 20 10
        //我需要10个，你有20个，最终成交数量也就是10个，反之亦是如此
        turnoverAmount = needAmount.compareTo(providerAmount) <= 0 ? needAmount : providerAmount;
        if (turnoverAmount.compareTo(BigDecimal.ZERO) == 0) {
            return null; // 无法成交
        }

        //3)设置吃单总的成交数据
        taker.setTradedAmount(taker.getTradedAmount().add(turnoverAmount));  //此买单--总的成交量(可能还没买够)
        BigDecimal turnoverTaker = turnoverAmount.multiply(dealPrice).setScale(orderBooks.getCoinScale(), RoundingMode.HALF_UP);
        taker.setTurnover(taker.getTurnover().add(turnoverTaker));  //买单--总的成交额

        //4)设置挂单总的成交数据
        marker.setTradedAmount(marker.getTradedAmount().add(turnoverAmount));
        BigDecimal markerTurnover = turnoverAmount.multiply(dealPrice).setScale(orderBooks.getBaseCoinScale(), RoundingMode.HALF_UP);
        marker.setTurnover(marker.getTurnover().add(markerTurnover));

        ExchangeTrade exchangeTrade = new ExchangeTrade();
        exchangeTrade.setAmount(turnoverAmount); // 设置本次购买的数量
        exchangeTrade.setPrice(dealPrice);  // 设置购买的价格
        exchangeTrade.setTime(System.currentTimeMillis()); // 设置成交的时间
        exchangeTrade.setSymbol(orderBooks.getSymbol());  // 设置成交的交易对
        exchangeTrade.setDirection(taker.getOrderDirection());  // 设置交易的方法

        exchangeTrade.setSellOrderId(marker.getOrderId()); // 设置出售方的id
        exchangeTrade.setBuyOrderId(taker.getOrderId()); // 设置买方的id

        exchangeTrade.setBuyTurnover(turnoverTaker); // 设置本次买方的交易额
        exchangeTrade.setSellTurnover(markerTurnover); // 设置本次卖方的交易额
        /**
         * 处理盘口【一次挂单交易完成，扣减 盘口挂单数据】:
         *      我们的委托单肯定是: 将挂单的数据做了一部分消耗
         *
         *      委托单过来，直接消耗挂单数据，知道委托单圆满交易，不会出现在盘口
         *      若本次交易委托单没有吃单不够，则新增盘口数据(原本盘口中并没有该委托单数据，有的都是处理过剩下没法处理的)
         */
        if (marker.getOrderDirection() == OrderDirection.BUY) { //买入盘口
            // 减少挂单的数据量【从盘口里面移除数据： 委托量 - 成交量】
            orderBooks.getBuyTradePlate().remove(marker, turnoverAmount);
        } else { //卖出盘口
            orderBooks.getSellTradePlate().remove(marker, turnoverAmount);
        }

        return exchangeTrade;  //已完成的交易记录【只要成交一次,就产生一个记录】
    }

    /**
     * 【1.2】计算本次的 买单需要/挂单能提供的 交易数量
     *
     * @param order
     * @return
     */
    private BigDecimal calcTradeAmount(Order order) {
        //需要20个，可能已经买了16个，还需要4个
        return order.getAmount().subtract(order.getTradedAmount());
    }

    //-----------撮合系统只负责撮合业务，其他的发送到MQ由别人处理----------------------------

    /**
     * 【1.3】发送盘口数据,供以后我们前端的数据更新
     *
     * @param tradePlate
     */
    private void sendTradePlateData(TradePlate tradePlate) {
        Message<TradePlate> message = MessageBuilder
                .withPayload(tradePlate)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
        source.plateOut().send(message);
    }

    /***
     * 【1.4】处理已经成交的委托订单【自己完成的交易委托单/商家的委托单】          历史委托单
     * @param completedOrders
     */
    private void completedOrders(List<Order> completedOrders) {

        Message<List<Order>> message = MessageBuilder
                .withPayload(completedOrders)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
        source.completedOrdersOut().send(message);
    }

    /**
     *【1.5】 处理订单--交易记录                                               (右边)显示交易完成的记录
     *      *
     * @param exchangeTrades
     */
    private void handlerExchangeTrades(List<ExchangeTrade> exchangeTrades) {

        Message<List<ExchangeTrade>> message = MessageBuilder
                .withPayload(exchangeTrades)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
        source.exchangeTradesOut().send(message);
        log.info("本次成交的记录为:" + exchangeTrades);

    }



}
